
import {ø} from "../../util/objects.js";
import * as utc from "../../util/utc.js";
import {isNonKioskIFrameContext} from "../../util/context.js";
import {describeSurface, nav1hr, nav3hr, newProduct, resolveValidTime} from "../productUtils.js";
import {tr} from "../../ux/translations.js";
import * as sources from "../sources.js";
import {buildGFSWind} from "./gfs-wind.js";
import {scalarProduct} from "../scalarProduct.js";
import {buildGFSWindPowerDensity} from "./gfs-wpd.js";
import {mulvec2, length} from "../../util/math.js";
import {createUnitDescriptors} from "../../util/units.js";
import {gaiaPath} from "../gaia.js";
import {createAirdensPalette} from "../../palette/airdens.js";
import {createCAPEPalette} from "../../palette/cape.js";
import {createMiseryPalette} from "../../palette/misery.js";
import {createMSLPPalette} from "../../palette/mslp.js";
import {createPrecipPalette} from "../../palette/precip.js";
import {createRHPalette} from "../../palette/rh.js";
import {createTCWPalette} from "../../palette/tcw.js";
import {createTPWPalette} from "../../palette/tpw.js";
import {createTempPalette} from "../../palette/temp.js";
import {createWindPalette} from "../../palette/wind.js";
import {createWPDPalette} from "../../palette/wpd.js";


const NOV_24_2014 = +Date.UTC(2014, 10, 24);
const MAR_11_2015 = +Date.UTC(2015, 2, 11);
const JUN_22_2016 = +Date.UTC(2016, 5, 22);
const JAN_01_2021 = +Date.UTC(2021, 0, 1);

let palettes = {};

/**
 * @param attr
 * @param {String} type
 * @param {Object} alignment date parts
 * @param {String?} z_level
 * @returns {String}
 */
function gfsPath(attr, type, alignment, z_level) {
    const {time_cursor, hd_enabled} = attr;
    const validTime = resolveValidTime(time_cursor, alignment);
    const dir = time_cursor === "current" ? "current" : utc.print(validTime, "{YYYY}/{MM}/{DD}");
    const stamp = time_cursor === "current" ? "current" : utc.print(validTime, "{hh}{mm}");

    const validTimestamp = utc.date(validTime);
    const format = validTimestamp >= NOV_24_2014 ? ".epak" : ".json";
    let res = "0.5";
    if (validTimestamp < MAR_11_2015 || isNonKioskIFrameContext()) {
        res = "1.0";
    } else if (validTimestamp >= JUN_22_2016 && hd_enabled) {
        res = "0.25";
    }

    const surface = z_level === "surface" ? "surface" : z_level !== undefined ? "isobaric" : undefined;
    const level = z_level === "surface" ? "level" : z_level !== undefined ? z_level : undefined;
    const file = [stamp, type, surface, level, "gfs", res].filter(e => e !== undefined).join("-") + format;
    return gaiaPath("data/gfs", dir, file);
}

function newGFSProduct(attr) {
    const {time_cursor} = attr;
    const base = {
        sourceHTML: sources.gfs,
    };
    const nav = time_cursor === "current" || utc.date(time_cursor) >= JAN_01_2021 ?
        nav1hr(time_cursor) :
        nav3hr(time_cursor);
    return ø(newProduct(), base, nav);
}

export function createWindLayer(attr) {
    const base = newGFSProduct(attr);
    return ø(base, {
        type: "wind",
        descriptionHTML: {
            name: tr`Wind`,
            qualifier: ` @ ${describeSurface(attr)}`,
        },
        paths: [gfsPath(attr, "wind", base.alignment(), attr.z_level)],
        builder: buildGFSWind,
        unitDescriptors: createUnitDescriptors({
            "km/h": {convert: uv => mulvec2(uv, 3.6),      scalarize: length, precision: 0, convention: "into"},
            "m/s":  {                                      scalarize: length, precision: 1, convention: "into"},
            "kn":   {convert: uv => mulvec2(uv, 1.943844), scalarize: length, precision: 0, convention: "into"},
            "mph":  {convert: uv => mulvec2(uv, 2.236936), scalarize: length, precision: 0, convention: "into"},
        }),
        supported_z_levels: ["surface", "1000hPa", "850hPa", "700hPa", "500hPa", "250hPa", "70hPa", "10hPa"],
        scale: palettes.wind ??= createWindPalette(),
        particles: {velocityScale: 1/100, maxIntensity: 15},
    });
}

export function createTempLayer(attr) {
    const selector = /Temperature/;  // "Temperature_isobaric", "Temperature_height_above_ground"
    const base = newGFSProduct(attr);
    return ø(base, {
        type: "temp",
        descriptionHTML: {
            name: tr`Temperature`,
            qualifier: ` @ ${describeSurface(attr)}`,
        },
        paths: [gfsPath(attr, "temp", base.alignment(), attr.z_level)],
        builder: file => scalarProduct(file, selector, {hasMissing: false, legacyName: "Temperature"}),
        unitDescriptors: createUnitDescriptors({
            "°C": {convert: x => x - 273.15,       precision: 1},
            "°F": {convert: x => x * 9/5 - 459.67, precision: 1},
            "K":  {convert: x => x,                precision: 1},
        }),
        supported_z_levels: ["surface", "1000hPa", "850hPa", "700hPa", "500hPa", "250hPa", "70hPa", "10hPa"],
        scale: palettes.temp ??= createTempPalette(),
    });
}

export function createRHLayer(attr) {
    const selector = /Relative_humidity/;  // "Relative_humidity_isobaric", "Relative_humidity_height_above_ground"
    const base = newGFSProduct(attr);
    return ø(base, {
        type: "relative_humidity",
        descriptionHTML: {
            name: tr`Relative Humidity`,
            qualifier: ` @ ${describeSurface(attr)}`,
        },
        paths: [gfsPath(attr, "relative_humidity", base.alignment(), attr.z_level)],
        builder: file => scalarProduct(file, selector, {hasMissing: false}),
        unitDescriptors: createUnitDescriptors({
            "%": {convert: x => x, precision: 0},
        }),
        supported_z_levels: ["surface", "1000hPa", "850hPa", "700hPa", "500hPa", "250hPa", "70hPa", "10hPa"],
        scale: palettes.rh ??= createRHPalette(),
    });
}

export function createAirDensLayer(attr) {
    const base = newGFSProduct(attr);
    return ø(base, {
        type: "air_density",
        descriptionHTML: {
            name: tr`Air Density`,
            qualifier: ` @ ${describeSurface(attr)}`,
        },
        paths: [gfsPath(attr, "air_density", base.alignment(), attr.z_level)],
        builder: file => scalarProduct(file, /air_density/, {hasMissing: false}),
        unitDescriptors: createUnitDescriptors({
            "kg/m^3": {convert: x => x, precision: 2},
        }),
        supported_z_levels: ["1000hPa", "850hPa", "700hPa", "500hPa", "250hPa", "70hPa", "10hPa"],
        scale: palettes.airdens ??= createAirdensPalette(),
    });
}

export function createWPDLayer(attr) {
    const windProduct = createWindLayer(attr);
    const airdensProduct = createAirDensLayer(attr);
    const base = newGFSProduct(attr);
    return ø(base, {
        type: "wind_power_density",
        descriptionHTML: {
            name: tr`Instantaneous Wind Power Density`,
            qualifier: ` @ ${describeSurface(attr)}`,
        },
        paths: [windProduct.paths[0], airdensProduct.paths[0]],
        builder: buildGFSWindPowerDensity,
        unitDescriptors: createUnitDescriptors({
            "kW/m^2": {convert: x => x / 1000, precision: 1},
            "W/m^2":  {convert: x => x,        precision: 0},
        }),
        supported_z_levels: ["1000hPa", "850hPa", "700hPa", "500hPa", "250hPa", "70hPa", "10hPa"],
        scale: palettes.wpd ??= createWPDPalette(),
    });
}

export function createMiseryIndexLayer(attr) {
    const perceived = ` ${tr`(feels like)`}`;
    const base = newGFSProduct(attr);
    return ø(base, {
        type: "misery_index",
        descriptionHTML: {
            name: tr`Misery Index`,
            qualifier: "",
        },
        paths: [gfsPath(attr, "misery_index", base.alignment())],
        builder: file => scalarProduct(file, /misery_index/, {hasMissing: false}),
        unitDescriptors: createUnitDescriptors({
            "°C": {html: "°C" + perceived, convert: x => x - 273.15,       precision: 1},
            "°F": {html: "°F" + perceived, convert: x => x * 9/5 - 459.67, precision: 1},
             "K": {html:  "K" + perceived, convert: x => x,                precision: 1},
        }),
        scale: palettes.misery ??= createMiseryPalette(),
    });
}

export function createTCWLayer(attr) {
    const selector = /Cloud_water/;  // "Cloud_water_entire_atmosphere", "Cloud_water_entire_atmosphere_single_layer"
    const base = newGFSProduct(attr);
    return ø(base, {
        type: "total_cloud_water",
        descriptionHTML: {
            name: tr`Total Cloud Water`,
            qualifier: "",
        },
        paths: [gfsPath(attr, "total_cloud_water", base.alignment())],
        builder: file => scalarProduct(file, selector, {hasMissing: false, legacyName: "Cloud_water"}),
        unitDescriptors: createUnitDescriptors({
            "kg/m^2": {convert: x => x, precision: 3},
        }),
        scale: palettes.tcw ??= createTCWPalette(),
    });
}

export function createTPWLayer(attr) {
    // "Precipitable_water_entire_atmosphere", "Precipitable_water_entire_atmosphere_single_layer"
    const selector = /Precipitable_water/;
    const base = newGFSProduct(attr);
    return ø(base, {
        type: "total_precipitable_water",
        descriptionHTML: {
            name: tr`Total Precipitable Water`,
            qualifier: "",
        },
        paths: [gfsPath(attr, "total_precipitable_water", base.alignment())],
        builder: file => scalarProduct(file, selector, {hasMissing: false, legacyName: "Precipitable_water"}),
        unitDescriptors: createUnitDescriptors({
            "kg/m^2": {convert: x => x, precision: 3},
        }),
        scale: palettes.tpw ??= createTPWPalette(),
    });
}

export function createMSLPLayer(attr) {
    const selector = /Pressure_reduced_to_MSL/;  // Pressure_reduced_to_MSL_msl
    const base = newGFSProduct(attr);
    return ø(base, {
        type: "mean_sea_level_pressure",
        descriptionHTML: {
            name: tr`Mean Sea Level Pressure`,
            qualifier: "",
        },
        paths: [gfsPath(attr, "mean_sea_level_pressure", base.alignment())],
        builder: file => scalarProduct(file, selector, {hasMissing: false, legacyName: "Pressure_reduced_to_MSL"}),
        unitDescriptors: createUnitDescriptors({
            "hPa":  {convert: x => x / 100,           precision: 0},
            "mmHg": {convert: x => x / 133.322387415, precision: 0},
            "inHg": {convert: x => x / 3386.389,      precision: 1},
        }),
        scale: palettes.mslp ??= createMSLPPalette(),
    });
}

export function create3HPALayer(attr) {
    const base = newGFSProduct(attr);
    return ø(base, {
        type: "precip_3hr",
        descriptionHTML: {
            name: tr`3-hour Precipitation Accumulation`,
            qualifier: "",
        },
        paths: [gfsPath(attr, "precip_3hr", base.alignment())],
        builder: file => scalarProduct(file, /precip_accumulation_3hr/, {hasMissing: false}),
        unitDescriptors: createUnitDescriptors({
            "mm":     {convert: x => x,        precision: 1},
            "in":     {convert: x => x / 25.4, precision: 2},
            "kg/m^2": {convert: x => x,        precision: 1},
        }),
        alpha: {single: 160, animated: 160},
        scale: palettes.precip ??= createPrecipPalette(),
    });
}

export function createCAPELayer(attr) {
    const base = newGFSProduct(attr);
    return ø(base, {
        type: "cape",
        descriptionHTML: {
            name: tr`Convective Available Potential Energy from Surface`,
            qualifier: "",
        },
        paths: [gfsPath(attr, "cape", base.alignment())],
        builder: file => scalarProduct(file, /Convective_available_potential_energy_surface/, {hasMissing: false}),
        unitDescriptors: createUnitDescriptors({
            "J/kg": {convert: x => x, precision: 0},
        }),
        alpha: {single: 160, animated: 140},
        scale: palettes.cape ??= createCAPEPalette(),
    });
}
